import type { OptimizationSplitChunksOptions, NormalModule, Compiler, Module } from '@rspack/core'
import { getCwd, cryptoAsyncChunkName, sortByAscii, vendorList, getPkgName } from 'ssr-common-utils'
import { resolve } from 'path'
import { writeFileSync } from 'fs'

const dependenciesMap: Record<string, string[]> = {}
const generateMap: Record<string, string> = {}
const chunkNameRe = /chunkName=(.*)/
const asyncChunkMap: Record<string, string[]> = {}

export const getSplitChunksOptions = () => {
	return {
		minSize: 0,
		maxAsyncRequests: 5,
		maxInitialRequests: 3,
		chunks: 'all',
		name(_, chunks, _cacheGroupKey) {
			return cryptoAsyncChunkName(chunks, asyncChunkMap)
		},
		cacheGroups: getWebpackSplitCache()
	} as OptimizationSplitChunksOptions
}

const directChunkModules = [] as NormalModule[]
export class splitChunkPlugin {
	apply(compiler: Compiler) {
		compiler.hooks.compilation.tap('splitChunkPlugin', (compilation) => {
			const moduleGraph = compilation.moduleGraph
			compilation.hooks.afterOptimizeModules.tap('splitChunkPlugin', (modules) => {
				for (const module of modules) {
					const { path: modulePath, query } = (module as NormalModule).resourceResolveData ?? {}
					if (!modulePath) {
						continue
					}
					if (query?.includes('chunkName')) {
						directChunkModules.push(module as NormalModule)
					}
					if (modulePath?.includes('client-entry')) {
						directChunkModules.push(module as NormalModule)
					}
				}
				for (const directChunkModule of directChunkModules) {
					const queue = [directChunkModule]
					const visited = new Set<string>()
					const moduleChunkName = chunkNameRe.exec(directChunkModule.resourceResolveData?.query ?? '')?.[1]! || 'Page'
					while (queue.length) {
						const currentModule = queue.shift()
						const { path: modulePath } = (currentModule as NormalModule).resourceResolveData ?? {}
						if (!modulePath) {
							continue
						}
						dependenciesMap[modulePath] = dependenciesMap[modulePath]
							? dependenciesMap[modulePath].concat(moduleChunkName)
							: [moduleChunkName]
						const outgoings = moduleGraph.getOutgoingConnections(currentModule as Module)
						for (const outgoing of outgoings) {
							const { path: modulePath } = (outgoing.resolvedModule as NormalModule).resourceResolveData ?? {}
							if (!modulePath) {
								continue
							}
							if (visited.has(modulePath)) {
								continue
							}
							visited.add(modulePath)
							queue.push(outgoing.resolvedModule as NormalModule)
						}
					}
				}
				for (const fileName in dependenciesMap) {
					let chunkNames = Array.from(new Set(dependenciesMap[fileName]))
					if (fileName.includes('node_modules')) {
						chunkNames.push('vendor')
					}
					const pkgName = getPkgName(fileName)
					if (vendorList.includes(pkgName)) {
						chunkNames = ['vendor']
					}
					chunkNames = chunkNames.sort(sortByAscii)
					if (chunkNames.includes('Page')) {
						chunkNames = chunkNames.includes('vendor') ? ['Page', 'vendor'] : ['Page']
					}
					dependenciesMap[fileName] = chunkNames.length === 0 ? ['Page'] : chunkNames
					generateMap[fileName] = dependenciesMap[fileName].join('~')
					asyncChunkMap[generateMap[fileName]] = chunkNames
				}
				writeFileSync(resolve(getCwd(), `./build/generateMap.json`), JSON.stringify(generateMap, null, 2))
				writeFileSync(resolve(getCwd(), `./build/dependenciesMap.json`), JSON.stringify(dependenciesMap, null, 2))
				writeFileSync(resolve(getCwd(), `./build/asyncChunkMap.json`), JSON.stringify(asyncChunkMap, null, 2))
			})
		})
	}
}

const getWebpackSplitCache = (): OptimizationSplitChunksOptions['cacheGroups'] => {
	return {
		dynamicChunks: {
			test: (module) => !!(module as NormalModule).resourceResolveData?.path,
			name: (module) => {
				const normalModule = module as NormalModule
				return generateMap[normalModule.resourceResolveData?.path ?? ''] ?? 'Page'
			}
		}
	} as OptimizationSplitChunksOptions['cacheGroups']
}
